package FIT::Runner::Context;

use FIT::Class;
use IO::All -utf8;
use FIT::Runner::Tally::Types;
use aliased 'FIT::Runner::Tally';
use FindBin;

# class we advise
use FIT::Runner;

my $Has_Been_Loaded;

# class and advice setup -------------------------------------------------------

has tally => (
    is       => 'ro',
    isa      => 'FIT::Runner::Tally',
    required => 1,
    handles  => [
        qw( unix_exit_code as_summary_line as_summary_text is_ok run_silently ),
        lc_tally_types,
    ],
    default  => sub { Tally->new },
);

has runner => (
    is       => 'ro',
    isa      => 'FIT::Runner',
    required => 1,
    weak_ref => 1,
    handles  => [qw(in_dir in_file out_dir out_file)],
);

has run_date => (is => 'ro', isa => 'Int', required => 1, default => sub { time });

sub BUILD {
    return if $Has_Been_Loaded; # do the following code only once per compile
    $Has_Been_Loaded = 1;

    # because of a violation of the ADP, FIT::Runner and this class depend
    # on each other, so we need to augment FIT::Runner as late as possible
    FIT::Runner->meta->add_around_method_modifier(run => \&inject_runner);
}

# around FIT::Runner::run
sub inject_runner() {
    my ($next, $runner, @args) = @_;
    # connect this runner's context to this runner's twig, for easy access to
    # the tally, IO, etc. for anyone who has an element
    $runner->twig->fit_context( $runner->context );
    my $rv = $next->($runner, @args);
    return $rv;
}

# get/set tally from twig
sub XML::Twig::fit_context() {
    my ($twig, $context) = @_;
    return $twig->{__FIT_Context__} if @_ == 1;
    $twig->{__FIT_Context__} = $context;
}

# get tally from any element in a twig
sub XML::Twig::Elt::fit_context() {
    my ($element, $context) = @_;
    # element could be created not in a twig at all, so definately no context
    return undef unless $element->twig;
    return $element->twig->fit_context;
}

# public interface -------------------------------------------------------------

sub parent { # could have no parent
    my $parent_runner = $self->runner->parent;
    return undef unless $parent_runner;
    return $parent_runner->context;
}

sub run_dir { io($FindBin::Bin)->absolute }

sub in_file_relative_to_out_file {
    my $path = $self->in_dir->absolute->abs2rel
        ( $self->out_dir->absolute->name );
    $path .= "/" if $path;
    return io($path. io($self->in_file)->filename);
}

# say you have relative link from the in_file to a file
# we call this in_dir relative
# but you want it $FindBin::Bin relative- so you can open it
sub make_in_file {
    my $href    = shift;
    my $in_dir  = $self->in_dir->name ne ''? ($self->in_dir. '/'): '';
    $href       = io( $in_dir. $href )->absolute;
    my $findbin = io($FindBin::Bin)->absolute;
    return io( $href->abs2rel($findbin->name) );
}

sub make_out_file {
    my $in_file = shift;
    my $out_dir = $self->out_dir->name ne ''? ($self->out_dir. '/'): '';
    return io($out_dir. $in_file);
}

sub absolute_to_out_dir_relative { io(shift)->abs2rel($self->out_dir . '/') }


=head1 NAME

FIT::Runner::Context - learn stuff about the current test run


=head1 SYNOPSIS

    my $element = ...;                   # get an element from the twig
    my $context = $element->fit_context; # get a context from the element
    my $parent  = $context->parent;      # parent context, if not top level
    my $when    = $context->run_date;    # when was this test started

    print $context->is_ok;               # is test document OK so far
    print $context->in_file;             # test input file
    ...
    # and other delegated FIT::Runner::Tally and FIT::Runner methods

    # helpers for maintaining all links relative

    #     get the run dir- $FindBin::Bin, as an absolute path
    #     all other files (except links in HTML) are given relative to this

    print $context->run_dir;
    
    #     get the in_file not relative to $FindBin::Bin, but instead
    #     relative to the out_file, so that links to it from out_file
    #     will work correctly
    
    print $context->in_file_relative_to_out_file;

    #     transform a path which is relative to in_dir, e.g. a link from
    #     in_file, into a path relative to $FindBin::Bin, so you can open it
    
    print $context->make_in_file("foo/bar.html");

    #     take a path relative to in_dir, and return a new path, relative
    #     to $FindBin::Bin, which is a good path to use as an out_file
    #     for this path- it will be under out_dir, but composed of all
    #     the path parts of the input argument

    print $context->make_out_file( "baz/bam.html");

    #     takes an absolute path and returns it relative to the out_dir

    print $context->absolute_to_out_dir_relative;


=head1 DESCRIPTION

A runner context models the common context between all tests in a single test
run. You access the context through the runner or through any element in a 
twig which is part of a test document.

=cut

